//
// Created by xuchao on 16-11-18.
//

#ifndef OKPG_OKPGCONNECTION_H
#define OKPG_OKPGCONNECTION_H

#include <iostream>
#include <assert.h>
#include <map>
#include <folly/futures/Future.h>
#include "OkPgUrl.h"
#include "OkPgUtils.h"
#include "OkPgResultSet.h"
#include "OkPgPreparedStatement.h"

namespace OKPg {

    using URL=OkPgUrl;
    typedef enum {
        STRING,
        INTEGER,
        //Array,
        //Object,
                UNKNOWN
    } ArgValueType;

    struct OkPgArgs {
        using ArgDef=std::tuple<ArgValueType, std::string, int>;
        using ArgListDef=std::map<std::string, ArgDef>;
        using ArgListIterator=std::map<std::string, ArgDef>::iterator;
        using ArgListConstIterator=std::map<std::string, ArgDef>::const_iterator;

    private:

        std::string functionName_;

        ArgListDef argList_;

    public:

        OkPgArgs &insertInto(const std::string &tableName, const u_int count = 0, ...) {

        }

        OkPgArgs &values() {

        }

        OkPgArgs &deleteFrom(const std::string &tableName) {

        }

        OkPgArgs &update(const std::string &tableName) {

        }

        OkPgArgs &set(const std::string &format, ...) {

        }

        OkPgArgs &select(const u_int count = 0, ...) {


        }

        OkPgArgs &from(const u_int count = 1, ...) {

        }

        OkPgArgs &where(const std::string &where, ...) {

        }

        OkPgArgs &function(const std::string &functionName) {

            functionName_ = functionName;
        }

        OkPgArgs &addArg(const std::string &argName, const std::string &argValue) {
            argList_.emplace(argName, std::make_tuple(STRING, argValue, K_NULL_INT));
            return *this;

        }

        OkPgArgs &addArg(const std::string &argName, const int argValue) {
            argList_.emplace(argName, std::make_tuple(INTEGER, K_NULL_STR, argValue));
            return *this;
        }

    public:

        int getArgCount() const {
            if (argList_.empty()) {
                return 0;
            }
            return argList_.size();
        }

        std::vector<std::string> getArgNameList() const {
            std::vector<std::string> argNameList;
            if (argList_.empty()) {
                return argNameList;
            }
            for (ArgListConstIterator vci = argList_.begin();
                 vci != argList_.end();
                 ++vci) {
                argNameList.push_back(vci->first);
            }
            return argNameList;
        }

        ArgValueType getArgValueType(const std::string &argName) const {
            ArgListConstIterator vci = argList_.find(argName);
            if (vci != argList_.end()) {
                return std::get<0>(vci->second);
            } else {
                return UNKNOWN;
            }
        }

        const std::string &getArgValueStr(const std::string &argName,
                                          const std::string &defaultVal = K_EMPTY_STR) const {
            ArgListConstIterator vci = argList_.find(argName);
            if (vci != argList_.end()) {
                return std::get<1>(vci->second);
            } else {
                return defaultVal;
            }

        }

        const int &getArgValueInt(const std::string &argName,
                                  const int &defaultVal = K_DEFAULT_INT) const {
            ArgListConstIterator vci = argList_.find(argName);
            if (vci != argList_.end()) {
                return std::get<2>(vci->second);
            } else {
                return defaultVal;
            }
        }

        std::string toSqlString() const {
            std::stringstream sqlSs;
            sqlSs << "select * from " << functionName_ << "(";
            if (argList_.empty()) {
                sqlSs << ")";
                return sqlSs.str();
            }
            int cuCount = 0;
            std::vector<std::string> argNameList = getArgNameList();
            for (std::vector<std::string>::const_iterator aci = argNameList.begin();
                 aci != argNameList.end();
                 ++aci) {
                const int x = cuCount + 1;
                sqlSs << *aci << " :=$" << x;
                if (cuCount != (int) argNameList.size() - 1) {
                    sqlSs << ",";
                }
                ++cuCount;
            }
            sqlSs << ")";
            // LOG(INFO)<<sqlSs.str();
            return sqlSs.str();
        };

    };

    using OkPgResultFuture = folly::Future<OkPgResultSet>;
    using OkPgResultPromise = folly::Promise<OkPgResultSet>;

    class OkPgConnection {
    private:
        URL url_;

        PGconn *conn_;

        PGresult *result_;

    protected:

        OkPgConnection(const URL &url);

        virtual ~OkPgConnection();

        OkPgPreparedStatement newPreparedStatement(const OkPgArgs &args);

    public:
        OkPgConnection(OkPgConnection &&con);

        OkPgConnection &operator=(OkPgConnection &&con);

        //no copyable
        OkPgConnection(OkPgConnection &con) = delete;

        OkPgConnection &operator=(OkPgConnection &con)= delete;

    public:

        static OkPgConnection *connNew(const URL &url);

        void connFree();

        bool ping();

        void beginTransaction();

        void commit();

        void rollBack();

        /**
         * execute cmd like insert update delete
         * @param cmd the command to execute
         * @return the rows changed
         */
        OkPgResultFuture execute(const string &cmd);

        OkPgResultFuture execute(const OkPgArgs &args);

        /**
         * execute cmd like select
         * @param cmd the command to execute
         * @return the rows changed
         */
        OkPgResultFuture executeQuery(const string &cmd);

        OkPgResultFuture executeQuery(const OkPgArgs &args);

    };

}


#endif //OKPG_OKPGCONNECTION_H
